package org.mika.asm;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;

import net.sf.cglib.asm.Type;
import net.sf.cglib.core.*;

final class ASMUtils {
	private ASMUtils() {}
		
	/**
	 * Generates all private constructors matching constructors of superclass
	 * @param emitter emitter constructors generated to
	 * @param superclass parent class 
	 */
	static void generateConstructors(ClassEmitter emitter, Class<?> superclass) throws Exception {
		Constructor<?>[] superConstructors = superclass.getConstructors();
		for (Constructor<?> constructor : superConstructors) {
			Signature signature = ReflectUtils.getSignature(constructor);
			CodeEmitter co = emitter.begin_method(Constants.ACC_PRIVATE, signature, null, null);
			co.load_this();
			for (int i = 0; i < constructor.getParameterTypes().length; i++) {
		      co.load_arg(i);
		    }
			co.invoke_constructor(emitter.getSuperType(), signature);
			co.return_value();
			co.end_method();
		}
	}
	
	/**
	 * Generates opener class header
	 * @param emitter emitter class header generated to
	 * @param su superclass
	 * @param interf implemented interface
	 * @param suffix class name suffix
	 */
	static void generateOpenerHeader(ClassEmitter emitter, Class<?> su, Class<?> interf) {
		String name = su.getName();
		
		if(name.startsWith("java.")) {
			name = "org." + name;
		}
		
		emitter.begin_class(
			Constants.V1_5,
			Constants.ACC_PUBLIC,
			name + "$$" + interf.getName().replace('.', '_') + "$$Opener",
			Type.getType(su),
			new Type[]{},
			"<generated>"
		);
	}
	
	static void generateWorkerHeader(ClassEmitter emitter, Class<?> su) {
		emitter.begin_class(
			Constants.V1_5,
			Constants.ACC_PUBLIC,
			su.getName() + "$$Bean",
			Type.getType(Object.class),
			new Type[]{ Type.getType(BeanWalker.class) },
			"<generated>"
		);
	}
	
	/**
	 * Generates implementator class header
	 */
	static void generateImplementatorHeader(ClassEmitter emitter, Class<?> opener, Class<?> iface) {
		emitter.begin_class(
			Constants.V1_5,
			Constants.ACC_PUBLIC,
			opener.getName() + "$$Implementator",
			Type.getType(Object.class),
			new Type[] { Type.getType(iface) },
			"<generated>"
		);
	}

	/**
	 * Declares private static HashMap
	 * @param emitter emitter HashMap declared to
	 * @param name    name of the HashMap
	 */
	static void declareHashMap(ClassEmitter emitter, String name) {
		emitter.declare_field(
			Constants.ACC_STATIC | Constants.ACC_PUBLIC,
			name,
			Type.getType(HashMap.class),
			null,
			null
		);		
	}
	
	/**
	 * Creates new instance of given class
	 * @param emitter emits to
	 * @param clazz class to create instance
	 */
	static void new_instance(CodeEmitter emitter, Class<?> clazz) {
		emitter.new_instance( Type.getType(clazz) );
		emitter.dup();
		emitter.invoke_constructor( Type.getType(clazz) );		
	}
	
	/**
	 * name = new HashMap();
	 * @param c class emitter
	 * @param o code emitter
	 * @param name name of hash map
	 */
	static void initHashMap(ClassEmitter c, CodeEmitter o, String name) {
		new_instance(o, HashMap.class);
		o.putstatic( c.getClassType() , name, Type.getType(HashMap.class) );
	}
	
	private static String WRAPPED = "$$wrapped";
	
	/**
	 * Generates $$wrapped field and constructor sets that field
	 * @param emitter  class emiter
	 * @param original original class
	 * @return name of the field
	 */
	static String generateWrapped(ClassEmitter emitter, Class<?> original) {
		emitter.declare_field(
			Constants.ACC_PRIVATE,
			WRAPPED, Type.getType( original ), null, null
		);
		
		CodeEmitter code = emitter.begin_method(
				Constants.ACC_PUBLIC,
				new Signature("<init>", Type.VOID_TYPE, new Type[]{ Type.getType( original ) }), 
				null, null
		);
		
		code.load_this();
		code.super_invoke_constructor();
		
		code.load_this();
		code.load_arg(0);
		code.putfield(WRAPPED);
        
		code.return_value();
		code.end_method();
		
		return WRAPPED;
	}
	
	/**
	 * Checks cast of returning type and returns last object in stack
	 * @param returnType
	 */
	static void returnFromStack(Class<?> returnType, CodeEmitter method) throws Exception {
		if(returnType != void.class) {
			if(returnType.isPrimitive()) {
				// ((Integer)result).intValue()
				if(returnType.equals(byte.class)) {
					method.checkcast( Type.getType( Byte.class ) );
					method.invoke(						
						ReflectUtils.getMethodInfo(
							Byte.class.getMethod("byteValue", new Class[]{})
						)
					);
				} else if(returnType.equals(short.class)) {
					method.checkcast( Type.getType( Short.class ) );
					method.invoke(						
						ReflectUtils.getMethodInfo(
							Short.class.getMethod("shortValue", new Class[]{})
						)
					);
				} else if(returnType.equals(int.class)) {
					method.checkcast( Type.getType( Integer.class ) );
					method.invoke(						
						ReflectUtils.getMethodInfo(
							Integer.class.getMethod("intValue", new Class[]{})
						)
					);					
				} else if(returnType.equals(long.class)) {
					method.checkcast( Type.getType( Long.class ) );
					method.invoke(						
						ReflectUtils.getMethodInfo(
							Long.class.getMethod("longValue", new Class[]{})
						)
					);					
				} else if(returnType.equals(float.class)) {
					method.checkcast( Type.getType( Float.class ) );
					method.invoke(						
						ReflectUtils.getMethodInfo(
							Long.class.getMethod("floatValue", new Class[]{})
						)
					);					
				} else if(returnType.equals(double.class)) {
					method.checkcast( Type.getType( Double.class ) );
					method.invoke(						
						ReflectUtils.getMethodInfo(
							Long.class.getMethod("doubleValue", new Class[]{})
						)
					);					
				} else if(returnType.equals(boolean.class)) {
					method.checkcast( Type.getType( Boolean.class ) );
					method.invoke(						
						ReflectUtils.getMethodInfo(
							Boolean.class.getMethod("booleanValue", new Class[]{})
						)
					);					
				}
			} else {		
				method.checkcast( Type.getType(returnType) );
			}
		} else {
			method.aconst_null();
		}
		
		method.return_value();				
		method.end_method();
	}
	
	/**
	 * Copies argument 0 as object
	 * @param method code emitter
	 * @param type type of argument
	 */
	static void copyArg(CodeEmitter method, Class<?> type) throws Exception {
		method.load_arg(0);				
		castToObject(method, type); 			
	}

	static void castToObject(CodeEmitter method, Class<?> type) throws NoSuchMethodException {
		if(type == byte.class) {
			method.invoke(						
				ReflectUtils.getMethodInfo(
					Byte.class.getMethod("valueOf", new Class[]{ byte.class })
				)
			);
		} if(type == short.class) {
			method.invoke(						
				ReflectUtils.getMethodInfo(
					Short.class.getMethod("valueOf", new Class[]{ short.class })
				)
			);
		} else if(type == int.class) {
			method.invoke(						
				ReflectUtils.getMethodInfo(
					Integer.class.getMethod("valueOf", new Class[]{ int.class })
				)
			);
		} else if(type == long.class) {
			method.invoke(						
				ReflectUtils.getMethodInfo(
					Long.class.getMethod("valueOf", new Class[]{ long.class })
				)
			);
		} else if(type == float.class) {
			method.invoke(						
				ReflectUtils.getMethodInfo(
					Float.class.getMethod("valueOf", new Class[]{ float.class })
				)
			);
		} else if(type == double.class) {
			method.invoke(						
				ReflectUtils.getMethodInfo(
					Double.class.getMethod("valueOf", new Class[]{ double.class })
				)
			);
		} else if(type == boolean.class) {
			method.invoke(						
				ReflectUtils.getMethodInfo(
					Boolean.class.getMethod("valueOf", new Class[]{ boolean.class })
				)
			);
		}
	}
	
	/**
	 * Copies m's arguments for invoke another method wits same arts
	 * @param method code emitter
	 * @param m method to copy args from
	 * @throws Exception
	 */
	static void copyArgs(CodeEmitter method, Method m) throws Exception {
		method.push(m.getParameterTypes().length);
		method.newarray( Type.getType(Object.class) );
		
		for(int i = 0; i < m.getParameterTypes().length; i++) {
			method.dup();
			method.push(i);
			method.load_arg(i);
			if(m.getParameterTypes()[i].isPrimitive()) {
				Class<?> type = m.getParameterTypes()[i];
				castToObject(method, type); 
			}				
			method.aastore();
		} 
	}
}
